﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sustainalytics.ClientDW.DataAccessLayer.ESGRatings;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.DataLayer.Shared;
using Sustainalytics.Entities;
using Sustainalytics.Entities.ESGRatings;
using Sustainalytics.ESGRatings.BusinessLogic.Computing;
using Sustainalytics.ESGRatings.Computing.Entities;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.Utils;
using CompanyWeights = Sustainalytics.ESGRatings.Computing.Entities.ESG.CompanyWeights;
using IndicatorTemplate = Sustainalytics.ClientDW.Entities.IndicatorTemplate;
using RawScore = Sustainalytics.ESGRatings.Computing.Entities.RawScore;
using ReferenceUniverse = Sustainalytics.Entities.ESGRatings.ReferenceUniverse;
using ReferenceUniverseCompany = Sustainalytics.ESGRatings.Computing.Entities.ESG.ReferenceUniverseCompany;
using WeightMatrix = Sustainalytics.Entities.ESGRatings.WeightMatrix;
using Sustainalytics.ESGRatings.Computing.Entities.ESG;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;

namespace Sustainalytics.HistoricalData.ComputationTool
{
    public interface IExclusionStrategy
    {

    }


    public class HistoricalDataSyncTool
    {
        private readonly IRepositoryCollection<CompanyHistoricalPerformance> _companyHistoricalPerformanceLast36MonthsRepository;
        private readonly IRepositoryCollection<CorporateData> _dwRep;
        private readonly IRepositoryCollection<ESGProfile> _esgProfileRepository;
        private readonly IRepositoryCollection<CompanyHistoricalDataIndicators> _historicalIndicatorsDataRepository;
        private readonly IRepositoryCollection<IndicatorTemplate> _indicatorTemplateRepository;
        private readonly IReferenceUniversesRepository _universeRepository;
        private readonly IWeightMatricesRepository _weightMatrixRepository;

        public HistoricalDataSyncTool(
            IRepositoryCollection<CorporateData> dwRep,
            IRepositoryCollection<CompanyHistoricalDataIndicators> historicalIndicatorsDataRepository,
            IReferenceUniversesRepository universeRepository,
            IWeightMatricesRepository weightMatrixRepository,
            IRepositoryCollection<ESGProfile> esgProfileRepository,
            IRepositoryCollection<CompanyHistoricalPerformance> companyHistoricalPerformanceLast36MonthsRepository,
            IRepositoryCollection<IndicatorTemplate> indicatorTemplateRepository)
        {
            _dwRep = dwRep;
            _historicalIndicatorsDataRepository = historicalIndicatorsDataRepository;
            _universeRepository = universeRepository;
            _weightMatrixRepository = weightMatrixRepository;
            _esgProfileRepository = esgProfileRepository;
            _companyHistoricalPerformanceLast36MonthsRepository = companyHistoricalPerformanceLast36MonthsRepository;
            _indicatorTemplateRepository = indicatorTemplateRepository;
        }

        private static int RawScoreThreshold
        {
            get { return 95; }
        }

        public static bool CompanyExclusionPolicy(CompanyHistoricalDataIndicators item)
        {

            var groupedItems = from indicator in item.Indicators
                               let @theme = indicator.IndicatorNumber.Substring(0, 1).ToUpperInvariant()
                               group indicator by @theme
                                   into grouping
                               select new { Theme = grouping.Key, Indicators = grouping.Select(x => x) };

            var result = !groupedItems.All(x => x.Indicators.All(indicator => indicator.RawScore > RawScoreThreshold));
            return result;
        }

        public IEnumerable<CompanyHistoricalPerformance> ComputeHistoryData(DateTime referenceDate, int monthsNumber = 36, string esgProfileName = "Default")
        {
            ReferenceUniverse referenceUniverse;
            WeightMatrix weightMatrix;
            ESGProfile esgProfile;

            this.GetDefaultProfileItems(esgProfileName, out esgProfile, out referenceUniverse, out weightMatrix);

            // ReSharper disable once RedundantBoolCompare
            var corporateDataLite = _dwRep.ReadWhere(x => x.ComprehensiveEsgResearchState.ResearchState == ResearchState.ResearchAvailable, null, null, null, x => x.Id, x => x.ComprehensiveEsgResearchState, x => x.ControversyResearchState, x => x.CapitalIqId, x => x.CompanyName, x => x.CompanyType, x => x.GICSIndustryGroup, x => x.PeerGroupShortName, x => x.TemplateType, x => x.UniverseIds);

            var corporateDataIds =
                corporateDataLite //.Where(x => x.Id == Guid.Parse("6c6845ff-4d9e-4f4d-b920-962af834ef0a"))
                    .Select(x => x.Id).ToList();

            var historicalDataDict = new Dictionary<Guid, CompanyHistoricalPerformance>();

            var indicatorsTemplates = this._indicatorTemplateRepository.ReadAll();

            ESGRatingsOptionsDto optionsDto = new ESGRatingsOptionsDto { IncludeAggregatedScores = true };

            ComputationEngine engine = ComputationEngine.Instance;

            foreach (var computationMonth in this.GetComputationMonthsUntilReferenceData(referenceDate, monthsNumber))
            {
                var month1 = computationMonth;

                Console.WriteLine(computationMonth);

                var historicalData = this._historicalIndicatorsDataRepository.ReadWhere(x => x.ProcessedDate == month1 && corporateDataIds.Contains(x.CompanyId));

                var filteredIndicators = FilterIndicatorsByThemeAndNotNullValue(historicalData).ToList();

                if (filteredIndicators.IsNullOrEmpty())
                {
                    continue;
                }

                var corporatedataSubset = corporateDataLite.Join(filteredIndicators, cd => cd.Id, hist => hist.CompanyId, (cd, hist) => cd).ToList();

                var ce = CorporateDataToCompanyEntity(corporatedataSubset);

                var universeCompaniesData = corporatedataSubset.Select(x => new ReferenceUniverseCompany(x.Id, x.CapitalIqId.ToString(), x.CompanyName)).ToArray();

                var referenceUniverses = new[]
                                             {
                                                 new ESGRatings.Computing.Entities.ESG.ReferenceUniverse
                                                     {
                                                         Id = referenceUniverse.Id,
                                                         Name = referenceUniverse.Name,
                                                         Companies = universeCompaniesData
                                                     }
                                             };

                var indicatorTemplateTemplates = this.GetIndicatorsTemplate(filteredIndicators, indicatorsTemplates);

                {
                    var companiesRawScores = GetCompaniesHistoricalIndicatorsData(filteredIndicators);

                    var matrices = new[] { new ESGRatings.Computing.Entities.ESG.WeightMatrix { MatrixId = weightMatrix.Id, CompaniesWeights = GetCompanyHistoricalWeightsData(filteredIndicators) } };

                    var id = engine.AddESGSet(null, ce, indicatorTemplateTemplates, companiesRawScores, matrices, referenceUniverses);

                    var result = engine.ComputeESGRatings(id, weightMatrix.Id, referenceUniverse.Id, ESGRatingsOptions.GetComputingFlags(esgProfile.CompanyType, esgProfile.TemplateType), esgProfile.ComparativeGroup, ESGRatingsOptions.IncludeFlags.IncludeRatings | ESGRatingsOptions.IncludeFlags.IncludeAggregatedScores);

                    optionsDto.SetId = id;

                    var mapper = new ESGToHistoricalData(optionsDto, engine);

                    mapper.GetRatings(
                        computationMonth,
                        result,
                        corporatedataSubset.Select(x => x.Id).ToList(),
                        historicalDataDict);

                    engine.DeleteSet(id);
                }
            }

            engine.Dispose();
            return historicalDataDict.Values;
        }

        private ESGRatings.Computing.Entities.IndicatorTemplate[] GetIndicatorsTemplate(IEnumerable<CompanyHistoricalDataIndicators> historicalDataIndicators, IEnumerable<IndicatorTemplate> indicatorsTemplates)
        {
            var indicatorDictionary = new Dictionary<string, ESGRatings.Computing.Entities.IndicatorTemplate>();
            var acceptedThemeCodesSet = new HashSet<string>() { { "E" }, { "S" }, { "G" }, };

            var allIndicators = historicalDataIndicators.SelectMany(companyIndicators => companyIndicators.Indicators);

            foreach (var item in (from indicator in allIndicators
                                  let template = indicatorsTemplates.FirstOrDefault(x => x.Number == indicator.IndicatorNumber)
                                  where template != null && !indicatorDictionary.ContainsKey(template.Number)
                                  select new ESGRatings.Computing.Entities.IndicatorTemplate(
                                      indicator.IndicatorNumber,
                                      template.ThemeName.Substring(0, 1).ToUpperInvariant(),
                                      template.IndicatorCategory.Substring(0, 1).ToUpperInvariant())).Where(item => acceptedThemeCodesSet.Contains(item.Theme)))
            {
                indicatorDictionary.Add(item.Code, item);
            }

            return indicatorDictionary.Values.ToArray();
        }

        public static List<CompanyHistoricalDataIndicators> FilterIndicatorsByThemeAndNotNullValue(IList<CompanyHistoricalDataIndicators> historicalData)
        {
            var acceptedThemeCodesSet = new HashSet<string>() { { "E" }, { "S" }, { "G" }, };

            return (from companyData in historicalData
                    let indicators =
                        companyData.Indicators.Where(
                            indicator =>
                            acceptedThemeCodesSet.Contains(indicator.IndicatorNumber.Substring(0, 1).ToUpperInvariant())
                            && (indicator.RawScore != null && indicator.Weight != null)).ToList()
                    where !indicators.IsNullOrEmpty()
                    select new CompanyHistoricalDataIndicators
                    {
                        CompanyId = companyData.CompanyId,
                        Id = companyData.Id,
                        Indicators = indicators,
                        CapitalIQID = companyData.CapitalIQID,
                        ProcessedDate = companyData.ProcessedDate
                    }).ToList();
        }

        /// <exception cref="IOException">An I/O error occurred. </exception>

        public void WriteHistoricalDataToDatabase(IEnumerable<CompanyHistoricalPerformance> datas)
        {
            var companyHistoricalPerformances = datas as CompanyHistoricalPerformance[] ?? datas.ToArray();
            var entireHistory = companyHistoricalPerformances.SelectMany(x => x.Last36Months);

            var companyHistoryDatas = entireHistory as CompanyHistoryData[] ?? entireHistory.ToArray();
            Console.Write(companyHistoryDatas.Count());

            var shortHistory = companyHistoricalPerformances.Select(
                x => new CompanyHistoricalPerformance { Id = x.Id, Last36Months = x.Last36Months.OrderByDescending(d => d.Date).Take(36).OrderBy(d => d.Date).ToList() });

            BackUpAndRepopulateCollection(shortHistory, _companyHistoricalPerformanceLast36MonthsRepository);
        }

        private static CompanyRawScores[] GetCompaniesHistoricalIndicatorsData(IEnumerable<CompanyHistoricalDataIndicators> historicalData)
        {
            var acceptedThemeCodesSet = new HashSet<string>() { { "E" }, { "S" }, { "G" }, };
            return (from data in historicalData
                    select new CompanyRawScores(
                        data.CompanyId,
                        data.Indicators.Where(
                            theme =>
                            acceptedThemeCodesSet.Contains(theme.IndicatorNumber.Substring(0, 1).ToUpperInvariant()))
                        .Select(x => new RawScore(x.IndicatorNumber, x.RawScore ?? 0)))).ToArray();
        }

        private static CompanyWeights[] GetCompanyHistoricalWeightsData(IList<CompanyHistoricalDataIndicators> historicalData)
        {
            var acceptedThemeCodesSet = new HashSet<string>() { { "E" }, { "S" }, { "G" }, };
            return (from data in historicalData
                    select
                        new CompanyWeights
                        {
                            CompanyId = data.CompanyId,
                            IndicatorsData =
                                    data.Indicators.Where(
                                        theme =>
                                        acceptedThemeCodesSet.Contains(
                                            theme.IndicatorNumber.Substring(0, 1).ToUpperInvariant()))
                                    .Select(
                                        x =>
                                        new Weight(
                                            x.IndicatorNumber,
                                            x.Weight.HasValue ? Convert.ToSingle(x.Weight.Value) : 0f))
                                    .ToArray()
                        }).ToArray();
        }

        private static void BackUpAndRepopulateCollection<T>(IEnumerable<T> collection, IRepositoryCollection<T> repositoryCollection) where T : IEntity
        {
            var database = repositoryCollection.Collection().Database;

            bool renameDatabaseSuccess = repositoryCollection.Collection().Exists();
            if (renameDatabaseSuccess)
            {
                var
                    ancientCollectionName = repositoryCollection.Collection().Name;
                renameDatabaseSuccess =
                    !database.RenameCollection(ancientCollectionName, ancientCollectionName + "_BKP", true).Ok;
            }

            if (!renameDatabaseSuccess)
            {
                renameDatabaseSuccess = repositoryCollection.CreateOrUpdateBatch(collection);
            }

            if (renameDatabaseSuccess)
            {
                var
                    bkpCollectionName = repositoryCollection.Collection().Name + "_BKP";
                database.DropCollection(bkpCollectionName);
            }
        }

        private static ICollection<ESGCompanyEntity> CorporateDataToCompanyEntity(IEnumerable<CorporateData> corporateDataLite)
        {
            var ce = corporateDataLite.Select(x =>
            {
                var result = new ESGCompanyEntity
                {
                    Id = x.Id,
                    IndustryGroup = x.GICSIndustryGroup,
                    PeerGroup = x.PeerGroupShortName,
                };

                if (x.CompanyType.ToLower() == "public")
                {
                    result.Attributes |= ESGRatingsOptions.CompanyAttributesEnum.Public;
                }

                if (x.CompanyType.ToLower() == "private")
                {
                    result.Attributes |= ESGRatingsOptions.CompanyAttributesEnum.Private;
                }

                if (x.TemplateType == "Type A")
                {
                    result.Attributes |= ESGRatingsOptions.CompanyAttributesEnum.TypeA;
                }

                if (x.TemplateType == "Type B")
                {
                    result.Attributes |= ESGRatingsOptions.CompanyAttributesEnum.TypeB;
                }

                return result;
            });
            return ce.ToList();
        }

        private IEnumerable<DateTime> GetComputationMonthsUntilReferenceData(DateTime referenceDate, int month)
        {
            var firstDayOfMonth = new DateTime(referenceDate.Year, referenceDate.Month, 1, 0, 0, 0, 0, DateTimeKind.Utc);
            for (; month >= 0; month--)
            {
                yield return firstDayOfMonth.AddMonths(-Math.Abs(month));
            }
        }


        private void GetDefaultProfileItems(string esgProfileName, out ESGProfile defaultProfile, out ReferenceUniverse referenceUniverse, out WeightMatrix weightMatrix)
        {
            defaultProfile =
                _esgProfileRepository.ReadAllWhere(x => x.Name == esgProfileName).SingleOrDefault() ?? ESGProfile.DefaultProfile;

            if (defaultProfile.ReferenceUniverseId == default(Guid))
            {
                ESGProfile profile = defaultProfile;
                referenceUniverse =
                    _universeRepository.ListReferenceUniverses(true)
                        .FirstOrDefault(x => x.Name == profile.ReferenceUniverseName);
            }
            else
            {
                ESGProfile profile = defaultProfile;
                referenceUniverse =
                    _universeRepository.ListReferenceUniverses(true)
                        .FirstOrDefault(x => x.Id == profile.ReferenceUniverseId);
            }

            if (referenceUniverse != null)
            {
                defaultProfile.ReferenceUniverseId = referenceUniverse.Id;
                defaultProfile.ReferenceUniverseName = referenceUniverse.Name;
            }

            if (defaultProfile.MatrixId == default(Guid))
            {
                weightMatrix = _weightMatrixRepository.GetWeightMatrixByNameAndAccountId(defaultProfile.MatrixName, defaultProfile.AccountId);
            }
            else
            {
                weightMatrix = _weightMatrixRepository.GetWeightMatrix(defaultProfile.MatrixId);
            }
        }

        private MatrixStructure GetMatrixStructure(
            Guid matrixId,
            IEnumerable<IndicatorTemplate> indicatorsTemplates,
            IEnumerable<string> indicatorsData)
        {
            MatrixStructure ms = new MatrixStructure()
            {
                Id = matrixId,
                Name = "matrix_" + matrixId,
                IndicatorsDetails = new List<MatrixStructure.ElementStructure>()
            };
            var indicatorTemplates = indicatorsTemplates as IndicatorTemplate[] ?? indicatorsTemplates.ToArray();
            foreach (var indic in indicatorsData)
            {
                var template = indicatorTemplates.FirstOrDefault(x => x.Number == indic);
                if (template != null)
                {
                    var indicator = new MatrixStructure.ElementStructure
                    {
                        IndicatorName = indic,
                        ThemeName = template.ThemeName,
                        ThemeSymbol = template.ThemeName.Substring(0, 1).ToUpperInvariant(),
                        ClusterName = template.IndicatorCategory
                    };

                    ms.IndicatorsDetails.Add(indicator);
                }
            }

            return ms;
        }
    }
}